package net.dybai.glacier;

import org.apache.http.util.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    private static final int PART_SIZE_DEFAULT = 33554432; // 32MB
    private static final int PART_SIZE_64MB = 67108864;
    private static final int PART_SIZE_128MB = 134217728;
    private static final int PART_SIZE_256MB = 268435456;
    private static final int PART_SIZE_512MB = 536870912;
    private static final int PART_SIZE_1GB = 1073741824;

    private static void showUsage() {
        LOG.error("JVM Options:");
        LOG.error("-DLOG_PATH=PATH");
        LOG.error("    Specify log file store path. If the parent directory is not exist, will be first create it.");
        LOG.error("    If not Specify this option, The path where this program resides will be used to store the log files.");
        LOG.error("");

        LOG.error("Program Options:");
        LOG.error("-HELP");
        LOG.error("    Print a help message describing all of command-line options.");
        LOG.error("");

        LOG.error("-BACKUP");
        LOG.error("    Backup mode.");
        LOG.error("");

        LOG.error("-RESTORE");
        LOG.error("    Restore mode.");
        LOG.error("");

        LOG.error("-REGION=region");
        LOG.error("    Specify the region where the vault is located.");
        LOG.error("    See Regions and Endpoints for the current list of regions and their corresponding endpoints for all AWS services.");
        LOG.error("    Regions and Endpoints: https://docs.aws.amazon.com/general/latest/gr/rande.html");
        LOG.error("");

        LOG.error("-VAULT=VaultName");
        LOG.error("    Choosing a vault name in the region.");
        LOG.error("");

        LOG.error("-SRC=PATH");
        LOG.error("    Need upload/decrypt file or directory.");
        LOG.error("");

        LOG.error("-OUT=PATH");
        LOG.error("    The storage path of the decrypted file.");
        LOG.error("");

//        LOG.error("-TEMP_PATH=PATH");
//        LOG.error("    Specify the compress file and encrypt file store path.");
//        LOG.error("");

//        LOG.error("-KEEP_TEMP");
//        LOG.error("    Don't delete temporary files on exit.");
//        LOG.error("");

        LOG.error("-PASS=password");
        LOG.error("    Password on encrypt archive file.");
        LOG.error("");

        LOG.error("-LOG_SECRET");
        LOG.error("    Show secret info into log files, such as key/salt, etc.");
        LOG.error("    WARNING: If enable this option, May be LEAK such as KEY/SALT/PASSWORD, etc information.");
        LOG.error("");

        LOG.error("-DESC=DESCRIPTION");
        LOG.error("    The archive description that you are uploading in parts.");
        LOG.error("    If not set it, Default is 'Backup by $CurrentDateTime'.");
        LOG.error("");

        LOG.error("-EXCLUDE=PATH");
        LOG.error("    Excluded file or directory.");
        LOG.error("    Multiple files are specified more than once.");
        LOG.error("");

        LOG.error("-PART_SIZE=[64MB/128MB/256MB/512MB/1GB]");
        LOG.error("    Multipart upload part size.");
        LOG.error("    The default part size is 32MB.");
        LOG.error("");
    }

    public static void main(String[] args) {
        String password = null;
        String srcPath = null;
        String destPath = null;
        String region = null;
        String vaultName = null;
        String description = null;
        boolean isBackup = false;
        boolean isRestore = false;
        boolean isShowSecret = false;
        Set<String> allExcludeFiles = new HashSet<>();
        Set<String> excludeAbsFiles = new HashSet<>();
        Set<String> excludeWildcardFiles = new HashSet<>();
        int partSize = 0;

        for (String arg : args) {
            int idx = arg.indexOf('=');
            if (idx < 0) {
                // 解析无值参数
                switch (arg) {
                    case "-BACKUP": { // 备份
                        isBackup = true;
                        continue;
                    }
                    case "-HELP": {
                        showUsage();
                        return ;
                    }
                    case "-LOG_SECRET": { // 可以将机要数据输出在日志中
                        isShowSecret = true;
                        continue;
                    }
                    default: {
                        continue;
                    }
                    case "-RESTORE": { // 恢复备份
                        isRestore = true;
                        continue;
                    }
                }
            }
            String key = arg.substring(0, idx);
            String value = arg.substring(idx+1);
            // 解析有值参数
            switch (key) {
                case "-REGION": {
                    region = value;
                    LOG.debug("Region: " + region);
                    continue;
                }
                case "-VAULT": {
                    vaultName = value;
                    LOG.debug("Vault name: " + vaultName);
                    break;
                }
                case "-SRC": { // 上传的数据来源路径
                    LOG.debug("Source file: " + value);
                    srcPath = value;
                    continue;
                }
                case "-OUT": { // 解密后文件保存的路径
                    LOG.debug("Destination file: " + value);
                    destPath = value;
                    continue;
                }
                case "-PASS": { // 加密密码
                    password = value;
                    continue;
                }
                case "-DESC": {
                    description = value;
                    continue;
                }
                case "-EXCLUDE": {
                    allExcludeFiles.add(value);
                    continue;
                }
                case "-PART_SIZE": {
                    if ("64MB".equalsIgnoreCase(value)) {
                        partSize = PART_SIZE_64MB;
                    } else if ("128MB".equalsIgnoreCase(value)) {
                        partSize = PART_SIZE_128MB;
                    } else if ("256MB".equalsIgnoreCase(value)) {
                        partSize = PART_SIZE_256MB;
                    } else if ("512MB".equalsIgnoreCase(value)) {
                        partSize = PART_SIZE_512MB;
                    } else if ("1GB".equalsIgnoreCase(value)) {
                        partSize = PART_SIZE_1GB;
                    } else {
                        partSize = PART_SIZE_DEFAULT;
                    }
                    continue;
                }
                default: {
                    continue;
                }
            }
        }

        // 检查参数
        if (TextUtils.isEmpty(password)) {
            showUsage();
            return ;
        }

        if ((!isBackup) && (!isRestore)) {
            showUsage();
            return ;
        }
        if (isBackup && isRestore) {
            LOG.error("You can only choose between \"-BACKUP\" and \"-RESTORE\".");
            return ;
        }

        if (isBackup) {
            if (TextUtils.isEmpty(region)) {
                showUsage();
                return ;
            }

            if (TextUtils.isEmpty(vaultName)) {
                showUsage();
                return ;
            }

            if (TextUtils.isEmpty(srcPath)) {
                showUsage();
                return ;
            }
            classificationExcludeFiles(allExcludeFiles, excludeAbsFiles, excludeWildcardFiles);
            new Backup().backup(vaultName, region, new File(srcPath), password, isShowSecret
                    , partSize, description, excludeAbsFiles, excludeWildcardFiles);
        } else if (isRestore) {
            if (TextUtils.isEmpty(destPath)) {
                showUsage();
                return ;
            }

            try {
                AESUtils.fileDecrypt(new File(srcPath), new File(destPath), password, isShowSecret);
                LOG.info("Decrypt success.");
            } catch (Exception e) {
                LOG.error("Restore failed.", e);
            }
        }
    }

    private static void classificationExcludeFiles(Set<String> allExcludeFiles
            , Set<String> excludeAbsFiles, Set<String> excludeWildcardFiles) {
        for (Iterator<String> it = allExcludeFiles.iterator(); it.hasNext(); ) {
            String path = it.next();
            if ((path.indexOf('/') >= 0) || (path.indexOf('\\') >= 0)) {
                excludeAbsFiles.add(path);
            } else {
                excludeWildcardFiles.add(path);
            }
            it.remove();
        }
    }

}
